//
//  server.c
//  Batalla Naval
//
//  Created by Federico Raimondo on 4/24/13.
//  Copyright (c) 2013 ar.dc.uba.so. All rights reserved.
//

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h> 
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <modelo.h>
#include <decodificador.h>
#include <globales.h>
#include <stdio.h>
#include <pthread.h>

#define MAX_MSG_LENGTH 4096
#define MAX_JUGADORES 100
#define MAX_CONTROLADORES 100	// Un controlador para cada jugador

/* Setea un socket como no bloqueante */
int no_bloqueante(int fd) {
    int flags;
    /* Toma los flags del fd y agrega O_NONBLOCK */
    if ((flags = fcntl(fd, F_GETFL, 0)) == -1 )
        flags = 0;
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}


/* Variables globales del server */
int sock;											// Socket donde se escuchan las conexiones entrantes
struct sockaddr_in name, remote, name_controlador; 	// Direcciones
//char buf[MAX_MSG_LENGTH];							// Buffer de recepción de mensajes
int s[MAX_JUGADORES];								// Sockets de los jugadores
int ids[MAX_JUGADORES];								// Ids de los jugadores
Modelo * model = NULL;								// Puntero al modelo del juego
Decodificador *decoder[MAX_JUGADORES];				// Un decodificador por thread
int n, tamanio, tamanio_barcos;						// Variables de configuracion del juego
pthread_t thread_jugador[MAX_JUGADORES];			// Thread para atender a cada jugador
void * atender_jugador(void *arg);					// Rutina para atender a cada jugador
int sock_controladores;								// Socket de comunicación para controladores
int controlador[MAX_CONTROLADORES];					// Socket de comunicación de cada controlador
pthread_t thread_controladores;						// Thread para recibir los controladores
pthread_t thread_controlador[MAX_CONTROLADORES];						// Thread para atender cada controlador
void * recibir_controlador(void *arg);				// Rutina para recibir controladores
void * atender_controlador(void *arg);				// Rutina para atender controladores

/* Resetea el juego */
void reset() {
	if (model != NULL) {
		delete model;
	}
	
	model = new Modelo(n, tamanio, tamanio_barcos);
	
	for(int i = 0; i < MAX_JUGADORES; i++){
		if (decoder[i] != NULL) {
			delete decoder[i];
		}
		decoder[i] = new Decodificador(model);
	}
}

/* Acepta todas las conexiones entrantes */

void accept() {
	int t;
	for (int i = 0; i < n; i++) {

		//printf("Server>> Aceptando jugador: %i\n", i);

		t = sizeof(remote);
		//printf("Server>> t: %i\n", t);
		if ((s[i] = accept(sock, (struct sockaddr*) &remote, (socklen_t*) &t)) == -1) {
			perror("aceptando la conexión entrante");
			exit(1);
		} else {
		//	printf("Server>> fd: %i\n", s[i]);
		}
		//printf("Server>> Acepte jugador: %i\n", i);

		//~ ids[i] = -1;
		int flag = 1;
		setsockopt(s[i],            /* socket affected */
				IPPROTO_TCP,     /* set option at TCP level */
				TCP_NODELAY,     /* name of option */
				(char *) &flag,  /* the cast is historical */
				sizeof(int));    /* length of option value */

		
		int* j_id = new int; 
		
		// Le paso el id del jugador
		*j_id = i;

		// Creo un thread para atender al jugador
		pthread_create(&thread_jugador[i], NULL, atender_jugador, (void *) j_id);
		

		//printf("Server>> Cree un thread para el jugador: %i\n", i);
	}

	// Hago join de los threads para esperar que terminen todos.
	for (int i = 0; i < n; i++){
		pthread_join(thread_jugador[i], NULL);
		close(s[i]);
		
	}
}



/* Para anteder al controlador */
void * atender_controlador(void * arg) {
	int c_id = *((int *) arg);

	char buf[MAX_MSG_LENGTH];	// Un buffer por cada controlador

	//printf("Server>> Atendiendo controlador: %i\n", c_id);

	int recibido;
	std::string resp;
	while((recibido = recv(controlador[c_id], buf, MAX_MSG_LENGTH, 0))){
		if (recibido < 0) {
			perror("Recibiendo ");		
		} else if (recibido > 0) {
			buf[recibido]='\0';
			char * pch = strtok(buf, "|");
			while (pch != NULL) {
				//Ejecutar y responder
				resp = decoder[c_id]->decodificar(pch);
				send(controlador[c_id],resp.c_str(), resp.length() +1, 0);
				//~ pch = strtok(NULL, "|");
			}
		}
	}

	delete (int *) arg;		
	return NULL;
}


/* Para atender al i-esimo jugador */
void * atender_jugador(void *arg) {

	int j_id = *((int *) arg);
	char buf[MAX_MSG_LENGTH];	// Un buffer por cada jugador
	//printf("Server>> Atendiendo jugador: %i\n", j_id);

	int recibido;
	std::string resp;
	while((recibido = recv(s[j_id], buf, MAX_MSG_LENGTH, 0))){

		//printf("Server>> Recibiendo del jugador: %i\n", j_id);

		if (recibido < 0) {
			perror("Recibiendo ");
			
		} else if (recibido > 0) {
			buf[recibido]='\0';
			// Separo los mensajes por el caracter |
			char * pch = strtok(buf, "|");
			while (pch != NULL) {
				
				//printf("Server>> Separando msj del jugador: %i\n", j_id);

				// No muestro por pantalla los NOP, son muchos
				if (strstr(pch, "Nop") == NULL) {
					//printf("Server>> Recibido: %s\n", pch);
				}
				
				//printf("Server>> Decodificando msj del jugador: %i\n", j_id);
				//Decodifico el mensaje y obtengo una respuesta
				resp = decoder[j_id]->decodificar(pch);
				
				//printf("Server>> Decodifique msj del jugador: %i\n", j_id);

				//printf("Server>> Enviando msj del jugador: %i\n", j_id);

				// Envio la respuesta
				send(s[j_id],resp.c_str(), resp.length() +1, 0);
				
				//printf("Server>> Server Envie msj del jugador: %i\n", j_id);

				// No muestro por pantalla los NOP, son muchos
				if (strstr(pch, "Nop") == NULL) {
				//	printf("Server>> Server>> Resultado %s\n", resp.c_str());
				}
				
				// Si ya se cual es el jugador
				if (j_id != -1) {

				//	printf("Server>> Server>> Buscando eventos del jugador: %i\n", j_id);

					// Busco si hay eventos para enviar y los mando
					bool hayEventos = model->hayEventos(j_id);
					while(hayEventos) {

				//		printf("Server>> Server>> Hay eventos del jugador: %i\n", j_id);	

						resp = decoder[j_id]->encodeEvent(j_id);
				//		printf("Server>> Server>> Enviando evento %s", resp.c_str());
						send(s[j_id],resp.c_str(), resp.length() +1, 0);
						hayEventos = model->hayEventos(j_id);
					}
				}
				pch = strtok(NULL, "|");
			}

		}
	}

	delete (int *) arg;		
	return NULL;
}


void * recibir_controlador(void *arg){

	//printf("Server>> Server>> Dentro de Thread del controlador\n");
	
	int port_controlador = CONTROLLER_PORT;

	/* Crear socket sobre el que se lee: dominio INET, protocolo TCP (STREAM). */
	sock_controladores = socket(AF_INET, SOCK_STREAM, 0);
	if (sock_controladores < 0) {
		perror("abriendo socket");
		exit(1);
	}
	/* Crear nombre, usamos INADDR_ANY para indicar que cualquiera puede enviar aquí. */
	name_controlador.sin_family = AF_INET;
	name_controlador.sin_addr.s_addr = INADDR_ANY;
	name_controlador.sin_port = htons(port_controlador);
	if (bind(sock_controladores, (const struct sockaddr*) &name_controlador, sizeof(name_controlador))) {
		perror("binding socket");
		exit(1);
	}
	
	//printf("Server>> Server>> Bind de socket del controlador\n");

	/* Escuchar en el socket y permitir n conexiones en espera. */
	if (listen(sock_controladores, MAX_CONTROLADORES) == -1) {
		perror("escuchando");
		exit(1);
	}

	//printf("Server>> Listen de socket del controlador\n");

	// Acepto los controladores
	int t;
	for (int i = 0; i < MAX_CONTROLADORES; i++) {

	//	printf("Server>> Acepto controladores... i= %i\n", i);
		
		t = sizeof(remote);
		if ((controlador[i] = accept(sock_controladores, (struct sockaddr*) &remote, (socklen_t*) &t)) == -1) {
			perror("aceptando la conexión entrante");
			exit(1);
		}

	//	printf("Server>> Accept del controladori= %i\n", i);	

		int flag = 1;
		setsockopt(controlador[i],  /* socket affected */
				IPPROTO_TCP,     	/* set option at TCP level */
				TCP_NODELAY,     	/* name of option */
				(char *) &flag,  	/* the cast is historical */
				sizeof(int));    	/* length of option value */

		
		int* c_id = new int; 
		
		// Le paso el id del controlador
		*c_id = i;

		// Creo un thread para atender al controlador
		pthread_create(&thread_controlador[i], NULL, atender_controlador, (void *) c_id);

	//	printf("Server>> Creo thread del controladori= %i\n", i);
	}

	// Hago join de los threads para esperar que terminen todos.
	for (int i = 0; i < n; i++){
		pthread_join(thread_controlador[i], NULL);
		close(controlador[i]);
		
	}

	return NULL;
}

/*
 * Recibe 4 parametros:
 * argv[1]: Puerto
 * argv[2]: Cantidad de jugadores (N)
 * argv[3]: Tamanio del tablero
 * argv[4]: Tamanio total de los barcos
 */
int main(int argc, char * argv[]) {
	if (argc < 5) {
		printf("Server>> Faltan parametros\n");
		printf("Server>> Se espera ./server puerto jugadores tamanio_tablero tamanio_barcos\n");
		exit(1);
	}
	int port = atoi(argv[1]);
	n = atoi(argv[2]);
	tamanio = atoi(argv[3]);
	tamanio_barcos = atoi(argv[4]);
	
	inicializar();
	int port_controlador = CONTROLLER_PORT;

	// Recibo los controladores en un thread a parte
	pthread_create(&thread_controladores, NULL, recibir_controlador, NULL);

	
	printf("Server>> Escuchando en el puerto %d - controlador en %d\n", port, port_controlador);
	printf("Server>> Jugadores %d - Tamanio %d - Tamanio Barcos %d\n", n, tamanio, tamanio_barcos);
	reset();

	
	
	/* Crear socket sobre el que se lee: dominio INET, protocolo TCP (STREAM). */
	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0) {
		perror("abriendo socket");
		exit(1);
	}
	/* Crear nombre, usamos INADDR_ANY para indicar que cualquiera puede enviar aquí. */
	name.sin_family = AF_INET;
	name.sin_addr.s_addr = INADDR_ANY;
	name.sin_port = htons(port);
	if (bind(sock, (const struct sockaddr*) &name, sizeof(name))) {
		perror("binding socket");
		exit(1);
	}
	
	/* Escuchar en el socket y permitir n conexiones en espera. */
	if (listen(sock, n) == -1) {
		perror("escuchando");
		exit(1);
	}

	printf("Server>> Acepto jugadores...\n");
	accept();
	
	
	printf("Server>> Corriendo...\n");
	
	/*bool sale = false;
	while (!sale) {
		printf("Server>> Sale: \n");
		fd_set readfds;
		FD_ZERO(&readfds);
		for (int i = 0; i < n; i++) {
			FD_SET(s[i], &readfds);
		}
		select(s[n-1]+1, &readfds, NULL, NULL, NULL);
		// Esto no lo usamos, atendemos el jugador en cada thread
		for (int i = 0; i < n; i++) {
			if (FD_ISSET(s[i], &readfds)) {
				atender_jugador((void *)i);
			}
		}
		sale = model->termino();
	}*/
    /*printf("Server>> Termino el juego, cerrando\n");
	for (int i = 0; i < n; i++) {
		close(s[i]);
	}*/

	printf("Server>> Espero los threads\n");
	pthread_join(thread_controladores, NULL);
	close(sock_controladores);
	printf("Server>> Cierro sock_controladores...\n");

	close(sock);
	printf("Server>> Cierro sock...\n");
	return 0;
}
